#include <types.h>
#include <stdio.h>
#include <trap.h>
#include <picirq.h>
#include <fs.h>
#include <ramdisk.h>
#include <arm.h>
#include <assert.h>

#define IO_BASE0                RAMDISK_START
#define IO_BASE1                RAMDISK_START + 0x8000000

#define MAX_IDE                 1
#define MAX_NSECS               256
#define MAX_DISK_NSECS          0x10000000U //(256 Mo)
#define VALID_IDE(ideno)        (((ideno) >= 0) && ((ideno) < MAX_IDE) && (ide_devices[ideno].valid))

static const struct {
    uint32_t base;        // I/O Base
} channels[2] = {
    {IO_BASE0},
    {IO_BASE1},
};

#define IO_BASE(ideno)          (channels[ideno].base)

static struct ide_device {
    unsigned char valid;        // 0 or 1 (If Device Really Exists)
    unsigned int sets;          // Commend Sets Supported
    unsigned int size;          // Size in Sectors
    unsigned char model[41];    // Model in String
} ide_devices[MAX_IDE];

static int
ide_wait_ready(unsigned short iobase, bool check_error) {
    //~ int r;
    //~ while ((r = inb(iobase + ISA_STATUS)) & IDE_BSY)
        //~ /* nothing */;
    //~ if (check_error && (r & (IDE_DF | IDE_ERR)) != 0) {
        //~ return -1;
    //~ }
    return 0;
}

// TODO: check if ramdisk are there
void
ide_init(void) {
    static_assert((SECTSIZE % 4) == 0);
    unsigned short ideno;
    for (ideno = 0; ideno < MAX_IDE; ideno ++) {
        /* assume that no device here */
        //~ ide_devices[ideno].valid = 0;

        //~ iobase = IO_BASE(ideno);

        //~ /* wait device ready */
        //~ ide_wait_ready(iobase, 0);

        //~ /* step1: select drive */
        //~ outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4));
        //~ ide_wait_ready(iobase, 0);

        //~ /* step2: send ATA identify command */
        //~ outb(iobase + ISA_COMMAND, IDE_CMD_IDENTIFY);
        //~ ide_wait_ready(iobase, 0);

        //~ /* step3: polling */
        //~ if (inb(iobase + ISA_STATUS) == 0 || ide_wait_ready(iobase, 1) != 0) {
            //~ continue ;
        //~ }

        /* device is ok */
        ide_devices[ideno].valid = 1;

        //~ /* read identification space of the device */
        //~ unsigned int buffer[128];
        //~ insl(iobase + ISA_DATA, buffer, sizeof(buffer) / sizeof(unsigned int));

        //~ unsigned char *ident = (unsigned char *)buffer;
        //~ unsigned int sectors;
        //unsigned int cmdsets = *(unsigned int *)(ident + IDE_IDENT_CMDSETS);
        //~ /* device use 48-bits or 28-bits addressing */
        //~ if (cmdsets & (1 << 26)) {
            //~ sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA_EXT);
        //~ }
        //~ else {
            //~ sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA);
        //~ }
        //~ ide_devices[ideno].sets = cmdsets;
        ide_devices[ideno].size = 0x80000; // 256 mo per ramdisk

        /* check if supports LBA */
        //~ assert((*(unsigned short *)(ident + IDE_IDENT_CAPABILITIES) & 0x200) != 0);

        //~ unsigned char *model = ide_devices[ideno].model, *data = ident + IDE_IDENT_MODEL;
        //~ unsigned int i, length = 40;
        //~ for (i = 0; i < length; i += 2) {
            //~ model[i] = data[i + 1], model[i + 1] = data[i];
        //~ }
        //~ do {
            //~ model[i] = '\0';
        //~ } while (i -- > 0 && model[i] == ' ');

        cprintf("ramdisk %d: %10u(sectors).\n", ideno, ide_devices[ideno].size);
    }
}

bool
ide_device_valid(unsigned short ideno) {
    return VALID_IDE(ideno);
}

size_t
ide_device_size(unsigned short ideno) {
    if (ide_device_valid(ideno)) {
        return ide_devices[ideno].size;
    }
    return 0;
}

int
ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs) {
    assert(nsecs <= MAX_NSECS && VALID_IDE(ideno));
    assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS);
    uint32_t iobase = IO_BASE(ideno);
	
	//cprintf("secno:0x%08x, dst:0x%08x, iobase:0x%08x\n", secno, dst, iobase);

    int ret = 0;
	uint32_t sec = secno;
    for (; nsecs > 0; nsecs --, dst += SECTSIZE, sec += SECTSIZE) {
        if ((ret = ide_wait_ready(iobase, 1)) != 0) {
            goto out;
			}
		__memcpy((uint32_t *) dst, (uint32_t *) (iobase + (uint32_t) sec), SECTSIZE);
    }

out:
    return ret;
}

int
ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs) {
    assert(nsecs <= MAX_NSECS && VALID_IDE(ideno));
    assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS);
    uint32_t iobase = IO_BASE(ideno);
	
	//cprintf("secno:0x%08x, src:0x%08x, iobase:0x%08x\n", secno, src, iobase);

    int ret = 0;
	uint32_t sec = secno;
    for (; nsecs > 0; nsecs --, src += SECTSIZE, sec += SECTSIZE) {
        if ((ret = ide_wait_ready(iobase, 1)) != 0) {
            goto out;
        }
		__memcpy((uint32_t *) (iobase + (uint32_t) sec), src, SECTSIZE);
    }

out:
    return ret;
}

